/*
 * wpa_supplicant/hostapd / Debug prints
 * Copyright (c) 2002-2007, Jouni Malinen <j@w1.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Alternatively, this software may be distributed under the terms of BSD
 * license.
 *
 * See README and COPYING for more details.
 */

#include "includes.h"

#include "common.h"

#ifdef CONFIG_DEBUG_SYSLOG
#include <syslog.h>

static int wpa_debug_syslog = 0;
#endif /* CONFIG_DEBUG_SYSLOG */


#ifdef CONFIG_DEBUG_FILE
static FILE *out_file = NULL;
#endif /* CONFIG_DEBUG_FILE */
int wpa_debug_level = MSG_INFO;
int wpa_debug_show_keys = 0;
int wpa_debug_timestamp = 0;


#ifndef CONFIG_NO_STDOUT_DEBUG

void wpa_debug_print_timestamp(void)
{
    struct os_time tv;

    if (!wpa_debug_timestamp)
        return;

    os_get_time(&tv);
#ifdef CONFIG_DEBUG_FILE
    if (out_file) {
        fprintf(out_file, "%ld.%06u: ", (long) tv.sec,
                (unsigned int) tv.usec);
    } else
#endif /* CONFIG_DEBUG_FILE */
        printf("%ld.%06u: ", (long) tv.sec, (unsigned int) tv.usec);
}


#ifdef CONFIG_DEBUG_SYSLOG
void wpa_debug_open_syslog(void)
{
    openlog("wpa_supplicant", LOG_PID | LOG_NDELAY, LOG_DAEMON);
    wpa_debug_syslog++;
}


void wpa_debug_close_syslog(void)
{
    if (wpa_debug_syslog)
        closelog();
}


static int syslog_priority(int level)
{
    switch (level) {
        case MSG_MSGDUMP:
        case MSG_DEBUG:
            return LOG_DEBUG;
        case MSG_INFO:
            return LOG_NOTICE;
        case MSG_WARNING:
            return LOG_WARNING;
        case MSG_ERROR:
            return LOG_ERR;
    }
    return LOG_INFO;
}
#endif /* CONFIG_DEBUG_SYSLOG */


/**
 * wpa_printf - conditional printf
 * @level: priority level (MSG_*) of the message
 * @fmt: printf format string, followed by optional arguments
 *
 * This function is used to print conditional debugging and error messages. The
 * output may be directed to stdout, stderr, and/or syslog based on
 * configuration.
 *
 * Note: New line '\n' is added to the end of the text when printing to stdout.
 */
void wpa_printf(int level, const char *fmt, ...)
{
    va_list ap;
    /* @@@ debug for now @@@ */
    //wpa_debug_level = MSG_MSGDUMP;

    va_start(ap, fmt);
    if (level >= wpa_debug_level) {
#ifdef CONFIG_DEBUG_SYSLOG
        if (wpa_debug_syslog) {
            vsyslog(syslog_priority(level), fmt, ap);
        } else {
#endif /* CONFIG_DEBUG_SYSLOG */
            wpa_debug_print_timestamp();
#ifdef CONFIG_DEBUG_FILE
            if (out_file) {
                vfprintf(out_file, fmt, ap);
                fprintf(out_file, "\n");
            } else {
#endif /* CONFIG_DEBUG_FILE */
                vprintf(fmt, ap);
                printf("\n");
#ifdef CONFIG_DEBUG_FILE
            }
#endif /* CONFIG_DEBUG_FILE */
#ifdef CONFIG_DEBUG_SYSLOG
        }
#endif /* CONFIG_DEBUG_SYSLOG */
    }
    va_end(ap);
}


static void _wpa_hexdump(int level, const char *title, const u8 *buf,
        size_t len, int show)
{
    show = 1;
    size_t i;
    if (level < wpa_debug_level)
        return;
    wpa_debug_print_timestamp();
#ifdef CONFIG_DEBUG_FILE
    if (out_file) {
        fprintf(out_file, "%s - hexdump(len=%lu):",
                title, (unsigned long) len);
        if (buf == NULL) {
            fprintf(out_file, " [NULL]");
        } else if (show) {
            for (i = 0; i < len; i++)
                fprintf(out_file, " %02x", buf[i]);
        } else {
            fprintf(out_file, " [REMOVED]");
        }
        fprintf(out_file, "\n");
    } else {
#endif /* CONFIG_DEBUG_FILE */
        printf("%s - hexdump(len=%lu):", title, (unsigned long) len);
        if (buf == NULL) {
            printf(" [NULL]");
        } else if (show) {
            for (i = 0; i < len; i++)
                printf(" %02x", buf[i]);
        } else {
            printf(" [REMOVED]");
        }
        printf("\n");
#ifdef CONFIG_DEBUG_FILE
    }
#endif /* CONFIG_DEBUG_FILE */
}

void wpa_hexdump(int level, const char *title, const u8 *buf, size_t len)
{
    _wpa_hexdump(level, title, buf, len, 1);
}


void wpa_hexdump_key(int level, const char *title, const u8 *buf, size_t len)
{
    _wpa_hexdump(level, title, buf, len, wpa_debug_show_keys);
}


static void _wpa_hexdump_ascii(int level, const char *title, const u8 *buf,
        size_t len, int show)
{
    size_t i, llen;
    const u8 *pos = buf;
    const size_t line_len = 16;

    /* @@@ debug for now @@@ */
    show = 1;

    if (level < wpa_debug_level)
        return;
    wpa_debug_print_timestamp();
#ifdef CONFIG_DEBUG_FILE
    if (out_file) {
        if (!show) {
            fprintf(out_file,
                    "%s - hexdump_ascii(len=%lu): [REMOVED]\n",
                    title, (unsigned long) len);
            return;
        }
        if (buf == NULL) {
            fprintf(out_file,
                    "%s - hexdump_ascii(len=%lu): [NULL]\n",
                    title, (unsigned long) len);
            return;
        }
        fprintf(out_file, "%s - hexdump_ascii(len=%lu):\n",
                title, (unsigned long) len);
        while (len) {
            llen = len > line_len ? line_len : len;
            fprintf(out_file, "    ");
            for (i = 0; i < llen; i++)
                fprintf(out_file, " %02x", pos[i]);
            for (i = llen; i < line_len; i++)
                fprintf(out_file, "   ");
            fprintf(out_file, "   ");
            for (i = 0; i < llen; i++) {
                if (isprint(pos[i]))
                    fprintf(out_file, "%c", pos[i]);
                else
                    fprintf(out_file, "_");
            }
            for (i = llen; i < line_len; i++)
                fprintf(out_file, " ");
            fprintf(out_file, "\n");
            pos += llen;
            len -= llen;
        }
    } else {
#endif /* CONFIG_DEBUG_FILE */
        if (!show) {
            printf("%s - hexdump_ascii(len=%lu): [REMOVED]\n",
                    title, (unsigned long) len);
            return;
        }
        if (buf == NULL) {
            printf("%s - hexdump_ascii(len=%lu): [NULL]\n",
                    title, (unsigned long) len);
            return;
        }
        printf("%s - hexdump_ascii(len=%lu):\n", title, (unsigned long) len);
        while (len) {
            llen = len > line_len ? line_len : len;
            printf("    ");
            for (i = 0; i < llen; i++)
                printf(" %02x", pos[i]);
            for (i = llen; i < line_len; i++)
                printf("   ");
            printf("   ");
            for (i = 0; i < llen; i++) {
                if (isprint(pos[i]))
                    printf("%c", pos[i]);
                else
                    printf("_");
            }
            for (i = llen; i < line_len; i++)
                printf(" ");
            printf("\n");
            pos += llen;
            len -= llen;
        }
#ifdef CONFIG_DEBUG_FILE
    }
#endif /* CONFIG_DEBUG_FILE */
}


void wpa_hexdump_ascii(int level, const char *title, const u8 *buf, size_t len)
{
    _wpa_hexdump_ascii(level, title, buf, len, 1);
}


void wpa_hexdump_ascii_key(int level, const char *title, const u8 *buf,
        size_t len)
{
    _wpa_hexdump_ascii(level, title, buf, len, wpa_debug_show_keys);
}


int wpa_debug_open_file(const char *path)
{
#ifdef CONFIG_DEBUG_FILE
    if (!path)
        return 0;
    out_file = fopen(path, "a");
    if (out_file == NULL) {
        wpa_printf(MSG_ERROR, "wpa_debug_open_file: Failed to open "
                "output file, using standard output");
        return -1;
    }
#ifndef _WIN32
    setvbuf(out_file, NULL, _IOLBF, 0);
#endif /* _WIN32 */
#endif /* CONFIG_DEBUG_FILE */
    return 0;
}


void wpa_debug_close_file(void)
{
#ifdef CONFIG_DEBUG_FILE
    if (!out_file)
        return;
    fclose(out_file);
    out_file = NULL;
#endif /* CONFIG_DEBUG_FILE */
}

#endif /* CONFIG_NO_STDOUT_DEBUG */


#ifndef CONFIG_NO_WPA_MSG
static wpa_msg_cb_func wpa_msg_cb = NULL;

void wpa_msg_register_cb(wpa_msg_cb_func func)
{
    wpa_msg_cb = func;
}


void wpa_msg(void *ctx, int level, const char *fmt, ...)
{
    va_list ap;
    char *buf;
    const int buflen = 2048;
    int len;

    buf = os_malloc(buflen);
    if (buf == NULL) {
        wpa_printf(MSG_ERROR, "wpa_msg: Failed to allocate message "
                "buffer");
        return;
    }
    va_start(ap, fmt);
    len = vsnprintf(buf, buflen, fmt, ap);
    va_end(ap);
    wpa_printf(level, "%s", buf);
    if (wpa_msg_cb)
        wpa_msg_cb(ctx, level, buf, len);
    os_free(buf);
}


void wpa_msg_ctrl(void *ctx, int level, const char *fmt, ...)
{
    va_list ap;
    char *buf;
    const int buflen = 2048;
    int len;

    if (!wpa_msg_cb)
        return;

    buf = os_malloc(buflen);
    if (buf == NULL) {
        wpa_printf(MSG_ERROR, "wpa_msg_ctrl: Failed to allocate "
                "message buffer");
        return;
    }
    va_start(ap, fmt);
    len = vsnprintf(buf, buflen, fmt, ap);
    va_end(ap);
    wpa_msg_cb(ctx, level, buf, len);
    os_free(buf);
}
#endif /* CONFIG_NO_WPA_MSG */


#ifndef CONFIG_NO_HOSTAPD_LOGGER
static hostapd_logger_cb_func hostapd_logger_cb = NULL;

void hostapd_logger_register_cb(hostapd_logger_cb_func func)
{
    hostapd_logger_cb = func;
}


void hostapd_logger(void *ctx, const u8 *addr, unsigned int module, int level,
        const char *fmt, ...)
{
    va_list ap;
    char *buf;
    const int buflen = 2048;
    int len;

    buf = os_malloc(buflen);
    if (buf == NULL) {
        wpa_printf(MSG_ERROR, "hostapd_logger: Failed to allocate "
                "message buffer");
        return;
    }
    va_start(ap, fmt);
    len = vsnprintf(buf, buflen, fmt, ap);
    va_end(ap);
    if (hostapd_logger_cb)
        hostapd_logger_cb(ctx, addr, module, level, buf, len);
    else if (addr)
        wpa_printf(MSG_DEBUG, "hostapd_logger: STA " MACSTR " - %s",
                MAC2STR(addr), buf);
    else
        wpa_printf(MSG_DEBUG, "hostapd_logger: %s", buf);
    os_free(buf);
}
#endif /* CONFIG_NO_HOSTAPD_LOGGER */
